package za.org.jcicada.syshealth;

import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import za.org.jcicada.cfgmngr.IConfigurationClient;
import za.org.jcicada.cfgmngr.IConfigurationServer;
import za.org.jcicada.cfgmngr.impl.DefaultConfigurationClient;
import za.org.jcicada.genss.ServiceURN;
import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.genss.sensor.ISamplingStrategy;
import za.org.jcicada.genss.sensor.ISensor;
import za.org.jcicada.genss.sensor.ISensorObserver;
import za.org.jcicada.genss.sensor.ISensorSubject;
import za.org.jcicada.genss.sensor.ISensorValue;
import za.org.jcicada.genss.sensor.SensorURN;
import za.org.jcicada.genss.sensor.impl.DefaultSensorObserver;
import za.org.jcicada.genss.sensor.impl.InvalidSensorValue;
import za.org.jcicada.genss.sensor.impl.Sensor;
import za.org.jcicada.genss.sensor.impl.SensorStatusValue;
import za.org.jcicada.genss.sensor.impl.StrategyAuto;

public class SensorCache {
	
	/** Logger object used for logging messages. */
	private static final Log logger = LogFactory.getLog(SensorCache.class);

	private Map<SensorURN, Sensor> sensorMap = new HashMap<SensorURN, Sensor>();
	
	private Map<SensorURN, ISensorSubject> sensorSubjectMap = new HashMap<SensorURN, ISensorSubject>();
	
	private Map<SensorURN, Set<Callback>> sensorCallbacks = new HashMap<SensorURN, Set<Callback>>();
	
	private IConfigurationServer configServer;
	private IConfigurationClient configClient = new ConfigurationClient();
	private ISensorObserver sensorObserver = new SensorObserver();

	public interface Callback {
		void attached(SensorURN sensorURN, Sensor sensor);
	}
	
	public SensorCache(IConfigurationServer configServer) {
		this.configServer = configServer;
	}
	
	public void attach(Callback callback, SensorURN sensorURN) {
		Sensor sensor = sensorMap.get(sensorURN);
		if (sensor == null) {
			Set<Callback> callbacks = sensorCallbacks.get(sensorURN);
			if (callbacks == null) {
				callbacks = new HashSet<Callback>();
				sensorCallbacks.put(sensorURN, callbacks);
			}
			callbacks.add(callback);
			
			configServer.requestLocateService(configClient, sensorURN, "tcp");
		} else {
			callback.attached(sensorURN, sensor);
		}
	}
	
//	public void detach(Observer observer, SensorURN sensorURN) {
//		Sensor sensor = sensorMap.get(sensorURN);
//		sensor.deleteObserver(observer);
//		if (sensor.countObservers() == 0) {
//			ISensorSubject sensorSubject = sensorSubjectMap.remove(sensorURN);
//			Set<SensorURN> sensorURNs = new HashSet<SensorURN>();
//			sensorURNs.add(sensorURN);
//			sensorSubject.requestDetach(sensorObserver, sensorURNs);
//		}
//	}
	
	private class ConfigurationClient extends DefaultConfigurationClient {

		@Override
		public void replyLocateService(Result result, ServiceURN serviceURN, URI locationURI) {
			if (result.getCode() == SuccessCode.COMPLETE_SUCCESS) {
				if (locationURI.getScheme().equals("tcp")) {
					if (serviceURN.getService().equals(SensorURN.URN_SERVICE_SENSORS)) {
						ISensorSubject sensorSubject = SensorURISubjectCache.getInstance().getSensorSubject(locationURI);
						SensorURN sensorURN = (SensorURN) serviceURN;
						sensorSubjectMap.put(sensorURN, sensorSubject);
						Set<SensorURN> sensorURNs = new HashSet<SensorURN>();
						sensorURNs.add(sensorURN);
						sensorSubject.requestAttach(sensorObserver, sensorURNs);
					}
				}
			} else {
				logger.warn(result.getMessage());
			}
		}

	}
	
	private class SensorObserver extends DefaultSensorObserver {

		@Override
		public void informSensorType(SensorURN sensorURN, ISensor sensor) {
			sensorMap.put(sensorURN, (Sensor) sensor);
			Set<Callback> callbacks = sensorCallbacks.get(sensorURN);
			if (callbacks != null) {
				for (Callback callback : callbacks) {
					callback.attached(sensorURN, (Sensor) sensor);
				}
				sensorCallbacks.remove(sensorURN);
			}
		}

		@Override
		public void informSensor(SensorURN sensorURN, ISensorValue value) {
			Sensor sensor = sensorMap.get(sensorURN);
			if (sensor != null) {
				try {
					sensor.setValue(value);
				} catch (InvalidSensorValue e) {
					logger.error(String.format("Failed to set value for sensor %s", sensorURN), e);
				}
			}
		}

		@Override
		public void informSensorStatus(SensorURN sensorURN, SensorStatusValue statusValue) {
			Sensor sensor = sensorMap.get(sensorURN);
			if (sensor != null) {
				sensor.setStatusValue(statusValue);
			}
		}

		@Override
		public void replyAttach(Result result, Set<SensorURN> sensorURNs) {
			for (SensorURN sensorURN: sensorURNs) {
				ISamplingStrategy strategy = new StrategyAuto(sensorURN);
				sensorSubjectMap.get(sensorURN).requestSetSamplingStrategy(this, strategy);
			}
		}
	}
}
